#include <libge/libge.h>
#include "nbt.h"

#define gePrintDebug(...) ;

inline unsigned short bswap16(unsigned short x){
  return (x>>8) | (x<<8);
}

inline unsigned int bswap32(unsigned int x){
  return (bswap16(x&0xFFFF)<<16) | (bswap16(x>>16));
}

inline uint64_t bswap64(uint64_t x){
  return (((uint64_t)bswap32(x&0xFFFFFFFFull))<<32) | (bswap32(x>>32));
}

nbt_node* NbtParse(u8* data, int* size, int force_type){
	gePrintDebug(0, "NbtParse(0x%08X, 0x%08X, %d)\n", data, size, force_type);
//	gePrintBuffer(data, 16);
	int i;
	int len, list_type;
	int a;
	int16_t i16;
	int32_t i32;
	int64_t i64;
	float f32;
	double f64;
	nbt_node* tmp;
	u8* first_data = data;
	u8 type = data[0];
	if(force_type){
		type = force_type;
	}
	if(type == 0){
		*size = 1;
		return NULL;
	}
	gePrintDebug(0, "Parsing NBT Node type 0x%02X", type);
	nbt_node* nbt = (nbt_node*)geMalloc(sizeof(nbt_node));
	if(force_type == 0){
		data++;
		memcpy(&i16, data, sizeof(short));
		short name_len = bswap16(i16);
		data += 2;
		if(name_len > 0){
			nbt->name = (char*)geMalloc(name_len+1);
			strncpy(nbt->name, (char*)data, name_len);
			gePrintDebug(0, "  name_len : %d\n", name_len);
			data += name_len;
		}
	}
	gePrintDebug(3, " name \"%s\"\n", nbt->name);
	gePrintDebug(0, "data (%08X) %02X %02X %02X %02X %02X %02X %02X %02X\n", (t_ptr)data, data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7]);
	nbt->type = type;
	switch(type){
		case TAG_BYTE :
			gePrintDebug(0, "  Byte\n");
			nbt->payload.i8 = (char)data[0];
			data++;
			break;

		case TAG_SHORT :
			gePrintDebug(0, "  Short\n");
			memcpy(&i16, data, sizeof(short));
			nbt->payload.i16 = bswap16(i16);
			data += 2;
			break;

		case TAG_INT :
			gePrintDebug(0, "  Int (%p %p)\n", bswap32, data);
			memcpy(&i32, data, sizeof(int));
			nbt->payload.i32 = bswap32(i32);
			data += 4;
			break;

		case TAG_LONG :
			gePrintDebug(0, "  Long (%p %p)\n", bswap64, data);
			memcpy(&i64, data, sizeof(int));
			i64 <<= 32;
			memcpy(&i64, data+4, sizeof(int));
			nbt->payload.i64 = bswap64(i64);
			data += 8;
			break;

		case TAG_FLOAT :
			gePrintDebug(0, "  Float\n");
			memcpy(&f32, data, sizeof(float));
			nbt->payload.f32 = f32;
			data += 4;
			break;

		case TAG_DOUBLE :
			gePrintDebug(0, "  Double\n");
			memcpy(&f64, data, sizeof(double));
			nbt->payload.f64 = f64;
			data += 8;
			break;

		case TAG_BYTE_ARRAY :
			gePrintDebug(0, "  Byte array\n");
			memcpy(&i32, data, sizeof(int));
			nbt->payload.byte_array.size = bswap32(i32);
			nbt->payload.byte_array.data = (u8*)geMalloc(nbt->payload.byte_array.size);
			memcpy(nbt->payload.byte_array.data, &data[4], nbt->payload.byte_array.size);
			data += nbt->payload.byte_array.size + 4;
			break;

		case TAG_STRING :
			gePrintDebug(0, "  String\n");
			memcpy(&i16, data, sizeof(short));
			len = bswap16(i16);
			nbt->payload.str = (char*)geMalloc(len + 1);
			strncpy(nbt->payload.str, (char*)&data[2], len);
			gePrintDebug(0, "    ==> \"%s\"\n", nbt->payload.str);
			data += len + 2;
			break;

		case TAG_LIST :
			{
			gePrintDebug(0, "  List\n");
			list_type = data[0];
			memcpy(&i32, &data[1], sizeof(int));
			len = bswap32(i32);
			data += 5;
			nbt->payload.list = (nbt_list*)geMalloc(sizeof(nbt_list)*len);
			for(i=0; i<len; i++){
				a = 0;
				nbt->payload.list[i].node = NbtParse(data, &a, list_type);
				data += a;
				if(i == 0){
					nbt->payload.list[i].prev = NULL;
				}
				if(i > 0){
					nbt->payload.list[i-1].next = &nbt->payload.list[i];
					nbt->payload.list[i].prev = &nbt->payload.list[i-1];
					nbt->payload.list[i].next = NULL;
				}
			}
			if(len == 0){
				gePrintDebug(1, "NBT List is empty !\n");
				nbt->payload.list = NULL;
			}
			break;
			}

		case TAG_COMPOUND :
			gePrintDebug(0, "Compound\n");
			len = 0;
			do {
				gePrintDebug(0, "-From compound \"%s\" :\n", nbt->name);
				a = 0;
				gePrintDebug(0, "ello 1\n");
				tmp = NbtParse(data, &a, 0);
				gePrintDebug(0, "ello 2\n");
				data += a;
				nbt->payload.list = (nbt_list*)geRealloc(nbt->payload.list, sizeof(nbt_list)*(len+1));
				gePrintDebug(0, "ello 3\n");
				nbt->payload.list[len].node = tmp;
				gePrintDebug(0, "ello 4\n");
				len++;
			} while(tmp != NULL);
			for(i=0; i<len; i++){
				if(i == 0){
					nbt->payload.list[i].prev = NULL;
				}
				if(i > 0){
					nbt->payload.list[i-1].next = &nbt->payload.list[i];
					nbt->payload.list[i].prev = &nbt->payload.list[i-1];
					nbt->payload.list[i].next = NULL;
				}
			}
			break;

		case TAG_INT_ARRAY :
			gePrintDebug(0, "  Int array\n");
			memcpy(&i32, data, sizeof(int));
			nbt->payload.int_array.size = bswap32(i32);
			nbt->payload.int_array.data = (u32*)geMalloc(nbt->payload.int_array.size*sizeof(u32));
			memcpy(nbt->payload.int_array.data, &data[4], nbt->payload.int_array.size*sizeof(u32));
			data += nbt->payload.int_array.size*sizeof(u32) + 4;
			break;

		default:
			gePrintDebug(2, "Unrecognized NBT Tag !\n");
			while(1);
			break;
	}

	*size = ((t_ptr)data - (t_ptr)first_data);
	gePrintDebug(0, "NBTParse end (%d)\n", *size);
	return nbt;
}

nbt_node* NbtFindNode(nbt_node* parent, const char* name, bool recursive){
	if(parent->type != TAG_LIST && parent->type != TAG_COMPOUND){
		return NULL;
	}

	nbt_node* out = NULL;
	nbt_list* list = parent->payload.list;
	while(list != NULL){
		if(list->node){
			if(list->node->name && !strcmp(list->node->name, name)){
				out = list->node;
				break;
			}
			if(recursive && (list->node->type == TAG_LIST || list->node->type == TAG_COMPOUND)){
				out = NbtFindNode(list->node, name, recursive);
				if(out){
					break;
				}
			}
		}
		list = list->next;
	}

	return out;
}

void NbtFree(nbt_node* node){
	if(node->name){
		geFree(node->name);
	}
	if(node->type == TAG_LIST || node->type == TAG_COMPOUND){
		nbt_list* list = node->payload.list;
		while(list != NULL){
			if(list->node){
				NbtFree(list->node);
			}
			list = list->next;
		}
		geFree(node->payload.list);
	}else
	if(node->type == TAG_BYTE_ARRAY){
		geFree(node->payload.byte_array.data);
	}
	if(node->type == TAG_INT_ARRAY){
		geFree(node->payload.int_array.data);
	}
	if(node->type == TAG_STRING){
		geFree(node->payload.str);
	}
	geFree(node);
}
